#ifndef FHE_CORE_FUNCREGISTERER_H
#define FHE_CORE_FUNCREGISTERER_H

#include <fhe/core/NodeFactory.h>
#include <fhe/core/FuncDesc.h>

namespace fhe
{
  namespace core
  {
    class FHE_API FuncRegisterer
    {
    public:
      #define FUNCREG_id( z, n, data ) data
      
      #define FUNCREG_iter( z, n, unused ) \
        template <typename T, typename R BOOST_PP_COMMA_IF( n ) BOOST_PP_ENUM_PARAMS( n, typename A )> \
        FuncRegisterer( const std::string& nodeModule, const std::string& nodeName, const std::string& funcName, R (T::*f)( BOOST_PP_ENUM_PARAMS( n, A ) ) ) \
        { \
          IFuncDesc::Ptr func( new FuncDesc<T,R, BOOST_PP_ENUM_PARAMS( n, A ) BOOST_PP_COMMA_IF( n ) BOOST_PP_ENUM( BOOST_PP_SUB( FHE_ARGS, n ), FUNCREG_id, void ) >( f, funcName ) ); \
          if ( NodeFactory::instance().hasNodeDesc( nodeModule, nodeName ) ) \
          { \
            NodeFactory::instance().getNodeDesc( nodeModule, nodeName )->addFunc( func ); \
          } \
          else if ( NodeFactory::instance().hasNodeInterface( nodeModule, nodeName ) ) \
          { \
            NodeFactory::instance().getNodeInterface( nodeModule, nodeName )->addFunc( func ); \
          } \
          else \
          { \
            FHE_ERROR( "unknown node type %s.%s", nodeModule.c_str(), nodeName.c_str() ); \
          } \
        }
            
      BOOST_PP_REPEAT( FHE_ARGS, FUNCREG_iter, ~ )
      
      #undef CONSTFUNCREG_iter

      #define CONSTFUNCREG_iter( z, n, unused ) \
        template <typename T, typename R BOOST_PP_COMMA_IF( n ) BOOST_PP_ENUM_PARAMS( n, typename A )> \
        FuncRegisterer( const std::string& nodeModule, const std::string& nodeName, const std::string& funcName, R (T::*f)( BOOST_PP_ENUM_PARAMS( n, A ) ) const ) \
        { \
          IFuncDesc::Ptr func( new ConstFuncDesc<T,R, BOOST_PP_ENUM_PARAMS( n, A ) BOOST_PP_COMMA_IF( n ) BOOST_PP_ENUM( BOOST_PP_SUB( FHE_ARGS, n ), FUNCREG_id, void ) >( f, funcName ) ); \
          if ( NodeFactory::instance().hasNodeDesc( nodeModule, nodeName ) ) \
          { \
            NodeFactory::instance().getNodeDesc( nodeModule, nodeName )->addFunc( func ); \
          } \
          else if ( NodeFactory::instance().hasNodeInterface( nodeModule, nodeName ) ) \
          { \
            NodeFactory::instance().getNodeInterface( nodeModule, nodeName )->addFunc( func ); \
          } \
          else \
          { \
            FHE_ERROR( "unknown node type %s.%s", nodeModule.c_str(), nodeName.c_str() ); \
          } \
        }
            
      BOOST_PP_REPEAT( FHE_ARGS, CONSTFUNCREG_iter, ~ )
      
      #undef CONSTFUNCREG_iter
      
      #undef FUNCREG_id
    };
    
    #define FHE_FUNC( nodeModule, nodeName, func ) ::fhe::core::FuncRegisterer fhe_func_registerer_##nodeModule##_##nodeName##_##func( #nodeModule, #nodeName, #func, &nodeName::func );
  }
}

#endif
